
package de.justphil.tcg.tcgandroidclient.network.rest;

import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;

import de.justphil.tcg.tcgandroidclient.network.Connectivity;
import de.justphil.tcg.tcgandroidclient.network.NoInternetException;
import de.justphil.tcg.tcgandroidclient.util.TCGLog;

import android.content.Context;

public final class Http {
	
	/* ***************************************************************************
	 * class fields *************************************************************************** */
	// BASIC
	private static Http http; // for singleton
	private HttpClient httpClient;
	private HttpGet httpGet;
	private HttpPost httpPost;
	private HttpPut httpPut;
	private HttpDelete httpDelete;
	// automatically set by HttpMonitor (broadcast receiver)
	private static boolean connected;
	// indicates whether this Singleton HTTP object was already initialized
	private static boolean initialized;
	// save the response headers from the last HTTP request
	private Header[] rspHeaders;
	
	// tag this class with class name for logging purpose
	// private static final String TAG = "Http";
	// Enum with HTTP request methods
	public enum Method {
		GET, POST, PUT, DELETE
	}
	
	// general response handler
	private ResponseHandler<ResponseContainer> rspHandler =
			new ResponseHandler<ResponseContainer>() {
				
				public ResponseContainer handleResponse(HttpResponse response)
						throws ClientProtocolException, IOException {
				
					HttpEntity entity = response.getEntity();
					byte[] bytes;
					if (entity != null) {
						bytes = EntityUtils.toByteArray(entity);
					}
					else {
						bytes = new byte[0];
					}
					readRspHeaders(response);
					ResponseContainer rc =
							new ResponseContainer(bytes, response.getStatusLine().getStatusCode(),
									getRspHeaders());
					return rc;
				}
			};
	
			
			
	/* ***************************************************************************
	 * class methods *************************************************************************** */
	private Http() {
	
	} // lock constructor
	
	public synchronized void init(Connectivity conn, Context c) {
	
		if (conn != null && conn.isInternetConnection(c)) {
			connected = true;
		}
		else {
			connected = false;
		}
		HttpParams httpParams = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParams, 10000);
		HttpConnectionParams.setSoTimeout(httpParams, 10000);
		httpClient = new DefaultHttpClient(httpParams);
		initialized = true;
	}
	
	public boolean isInitialized() {
	
		return initialized;
	}
	
	public static synchronized Http getInstance() {
	
		if (http == null) {
			http = new Http();
			initialized = false;
		}
		return http;
	}
	
	public synchronized ResponseContainer req(String url, byte[] data,
			HashMap<String, String> reqHeaders, Method httpMethod) throws NoInternetException {
	
		if (!initialized) {
			throw new IllegalStateException("Http object was not initialized.");
		}
		/* <RICHTIGE METHODE> */
		if (connected) {
			switch (httpMethod) {
				case GET:
					return get(url, reqHeaders);
				case POST:
					return sendData(url, reqHeaders, data, Method.POST);
				case PUT:
					return sendData(url, reqHeaders, data, Method.PUT);
				case DELETE:
					return delete(url, reqHeaders);
				default:
					throw new IllegalArgumentException("HTTP method not supported.");
			}
		}
		else {
			// no Internet connection
			throw new NoInternetException("No Internet connection.");
		}
		/* </RICHTIGE METHODE> */
	}
	
	/* ############################################## private methods for the supported HTTP methods
	 * ############################################## */
	private ResponseContainer get(String url, HashMap<String, String> reqHeaders) {
	
		httpGet = new HttpGet(url);
		setReqHeaders(reqHeaders, httpGet);
		try {
			ResponseContainer ba = httpClient.execute(httpGet, rspHandler);
			return ba;
		}
		catch (ConnectTimeoutException e) {
			TCGLog.e(new Object[] {
					"REST Connection Timeout to url '", url, "'!" });
			return null;
		}
		catch (SocketException e) {
			TCGLog.e(new Object[] {
					"REST No route to url '", url, "'!" });
			return null;
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	private ResponseContainer sendData(String url, HashMap<String, String> reqHeaders, byte[] data,
			Method httpMethod) {
	
		if (httpMethod == Method.POST) {
			httpPost = new HttpPost(url);
		}
		else {
			httpPut = new HttpPut(url);
		}
		final byte[] d = data;
		if (d == null) {
			throw new IllegalStateException("The data byte Array must not be null!");
		}
		ContentProducer cp = new ContentProducer() {
			
			public void writeTo(OutputStream outstream) throws IOException {
			
				outstream.write(d);
				outstream.flush();
			}
		};
		HttpEntity entity = new EntityTemplate(cp);
		if (httpMethod == Method.POST) {
			httpPost.setEntity(entity);
			setReqHeaders(reqHeaders, httpPost);
		}
		else {
			httpPut.setEntity(entity);
			setReqHeaders(reqHeaders, httpPut);
		}
		try {
			if (httpMethod == Method.POST) {
				ResponseContainer ba = httpClient.execute(httpPost, rspHandler);
				// TODO: kill DEBUG code
				// Log.d(TAG, "### RESPONSE BODY:");
				// Log.d(TAG, new String(ba.getBytes()));
				return ba;
			}
			else {
				ResponseContainer ba = httpClient.execute(httpPut, rspHandler);
				// TODO: kill DEBUG code
				// Log.d(TAG, "### PUT RESPONSE BODY:");
				// Log.d(TAG, new String(ba.getBytes()));
				return ba;
			}
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	private ResponseContainer delete(String url, HashMap<String, String> reqHeaders) {
	
		httpDelete = new HttpDelete(url);
		setReqHeaders(reqHeaders, httpDelete);
		try {
			ResponseContainer ba = httpClient.execute(httpDelete, rspHandler);
			// TODO: kill DEBUG code
			// Log.d(TAG, "### RESPONSE BODY:");
			// Log.d(TAG, new String(ba.getBytes()));
			return ba;
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	private void setReqHeaders(HashMap<String, String> h, HttpRequest req) {
	
		// DEBUG
		// Log.v(TAG, "Size:" + h.size());
		for (Map.Entry<String, String> e : h.entrySet()) {
			req.addHeader(e.getKey(), e.getValue());
			// DEBUG
			// Log.v(TAG, "[setReqHeaders] KEY: " + e.getKey() + " ... VALUE: " + e.getValue());
		}
	}
	
	private void readRspHeaders(HttpResponse rsp) {
	
		rspHeaders = rsp.getAllHeaders();
	}
	
	public synchronized HashMap<String, String> getRspHeaders() {
	
		HashMap<String, String> hm = new HashMap<String, String>();
		for (int i = 0; i < rspHeaders.length; i++) {
			Header h = (Header) rspHeaders[i];
			hm.put(h.getName(), h.getValue());
		}
		return hm;
	}
	
	public synchronized Header getRspHeader(String name) {
	
		for (int i = 0; i < rspHeaders.length; i++) {
			Header h = (Header) rspHeaders[i];
			if (name.equals(h.getName())) {
				return h;
			}
		}
		return null;
	}
}
